移动端border 1px 方案总结

移动端1px 变粗的原因

为什么移动端css里面写了1px, 实际看起来比1px粗?

因为Retine屏的分辨率是普通屏幕的2倍,3倍,1px的边框在devicePixelRatio=2的retina屏下会显示成2px,在devicePixelRatio=3的retina屏下会显示成3px,所以在高清屏下看着1px总是感觉变胖了。也可以说是设备像素(物理像素)和屏幕密度(设备独立像素)比例不一致导致的。

解决方案

1.用小数来写px值

IOS8下已经支持带小数的px值, media query对应devicePixelRatio有个查询值-webkit-min-device-pixel-ratio, css可以写成这样:

1
2
3
4
5
6
7
.border { border: 1px solid #999 }
@media screen and (-webkit-min-device-pixel-ratio: 2) {
.border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
.border { border: 0.333333px solid #999 }
}

缺点: 安卓与低版本IOS不适用

2.border-image

这里写图片描述

这样的1张6X6的图片, 9宫格等分填充border-image, 这样元素的4个边框宽度都只有1px

1
2
3
4
5
6
@media screen and (-webkit-min-device-pixel-ratio: 2){ 
.border{
border: 1px solid transparent;
border-image: url(border.gif) 2 repeat;
}
}

缺点: 对于圆角样式, 将图片放大修改成圆角也能满足需求, 但这样无形中增加了border的宽度,存在多种边框颜色或者更改的时候麻烦

3.background渐变

背景渐变, 渐变在透明色和边框色中间分割, frozenUI用的就是这种方法, 借用它的上边框写法:

1
2
3
4
5
6
@media screen and (-webkit-min-device-pixel-ratio: 2){
.ui-border-t {
background-position: left top;
background-image: -webkit-gradient(linear,left bottom,left top,color-stop(0.5,transparent),color-stop(0.5,#e0e0e0),to(#e0e0e0));
}
}

这样更改颜色比border-image方便,兼容性可到IE10

缺点: 代码量大, 而且需要针对不同边框结构, frozenUI就定义9种基本样式而且这只是背景, 这样做出来的边框实际是在原本的border空间内部的, 如果元素背景色有变化的样式, 边框线也会消失.最后不能适应圆角样式。

4. :before, :after与transform

构建1个伪元素, 边框宽度设置为1px, 再以transform根据物理像素和设备独立像素比缩放到1/2或者1/3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@mixin border-1px($color){
position: relative;
&:after{
display: block;
content: '';
position: absolute;
width: 100%;
left: 0;
bottom: 0;
border-top: 1px solid $color;
}
}

@media (-webkit-min-device-pixel-ratio: 1.5),(min-device-pixel-ratio: 1.5) {
.border-1px{
&:after{
-webkit-transform: scaleY(0.7);
-moz-transform: scaleY(0.7);
-ms-transform: scaleY(0.7);
-o-transform: scaleY(0.7);
transform: scaleY(0.7);
}
}
}
@media (-webkit-min-device-pixel-ratio: 2),(min-device-pixel-radio: 2) {
.border-1px{
&:after{
-webkit-transform: scaleY(0.5);
-moz-transform: scaleY(0.5);
-ms-transform: scaleY(0.5);
-o-transform: scaleY(0.5);
transform: scaleY(0.5);
}
}
}
@media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-radio: 3) {
.border-1px{
&:after{
-webkit-transform: scaleY(0.33);
-moz-transform: scaleY(0.33);
-ms-transform: scaleY(0.33);
-o-transform: scaleY(0.33);
transform: scaleY(0.33);
}
}
}

缺点: 代码量也很大, 占据了伪元素, 容易引起冲突

5. flexible.js

这是淘宝移动端采取的方案, github的地址:https://github.com/amfe/lib-flexible. 前面已经说过1px变粗的原因就在于一刀切的设置viewport宽度, 如果能把viewport宽度设置为实际的设备物理宽度, css里的1px不就等于实际1px长了么. flexible.js就是这样干的。

里面的scale值指的是对ideal viewport的缩放, flexible.js检测到IOS机型, 会算出scale = 1/devicePixelRatio, 然后设置viewport。

1
2
3
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');

devicePixelRatio=2时输出meta如下, 这样viewport与ideal viewport的比是0.5, 也就与设备物理像素一致:

1
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

另外html元素上的font-size会被设置为屏幕宽的1/10, 这样css可以以rem为基础长度单位进行改写, 比如rem是28px, 原先的7px就是0.25rem. border的宽度能直接写1px。

1
2
3
4
5
6
7
8
9
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) { //大于540px可以不认为是手机屏
width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}

px和rem相互转换的计算方法会暴露在window.lib.flexible中. 这样可以为less/sass编写宏方法. 具体的css改写方法参照大漠的文章http://www.w3cplus.com/mobile/lib-flexible-for-html5-layout.html项目中特别指出了为了防止字体模糊, 出现奇数字号的字体, 字体的实际单位还是要以px为单位。

缺点: 不适用安卓, flexible内部做了检测 非iOS机型还是采用传统的scale=1.0, 原因在于安卓手机不一定有devicePixelRatio属性, 就算有也不一定能响应scale小于1的viewport缩放设置, 例如我的手机设置了scale=0.33333333, 显示的结果也与scale=1无异。

综合使用:
对于IOS, flexible.js处理的已经很好了, 对于Android,方法2,3,4结合起来大体可以满足要求. flexible.js虽然不适用于安卓, 但它里面的这一段代码可以用来做对安卓机的部署。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (!dpr && !scale) {
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}

这里对安卓做检测, 如果是安卓, js动态加载css:

1
2
3
4
5
var link = document.createElement('link');
link.setAttribute("rel","stylesheet");
link.setAttribute("type","text/css");
link.setAttribute("href",".......Android.css");
document.querySelector('head').appendChild(link);



完~